#include <ts7200.h>
#include <bwio.h>
#include <a1.h>

// transmit a character, but don't busy-wait, update the time and sensor readings
int putc(int channel, char c) 
{
	int *putc_flags, *putc_data;
	switch( channel ) 
	{
		case COM1:
			putc_flags = (int *)( UART1_BASE + UART_FLAG_OFFSET );
			putc_data = (int *)( UART1_BASE + UART_DATA_OFFSET );
			break;
		case COM2:
			putc_flags = (int *)( UART2_BASE + UART_FLAG_OFFSET );
			putc_data = (int *)( UART2_BASE + UART_DATA_OFFSET );
			break;
		default:
			return -1;
			break;
	}
	
	while ((*putc_flags & TXFF_MASK))
	{
		checkTime();
		// checkSensor();
	}
	
	*putc_data = c;
	return 0;
}

// receive a character, but don't busy-wait, update the time and sensor readings
int getc( int channel ) {
	int *getc_flags, *getc_data;
	unsigned char c;

	switch( channel ) {
	case COM1:
		getc_flags = (int *)( UART1_BASE + UART_FLAG_OFFSET );
		getc_data = (int *)( UART1_BASE + UART_DATA_OFFSET );
		break;
	case COM2:
		getc_flags = (int *)( UART2_BASE + UART_FLAG_OFFSET );
		getc_data = (int *)( UART2_BASE + UART_DATA_OFFSET );
		break;
	default:
		return -1;
		break;
	}
	while ((*getc_flags & RXFE_MASK))
	{
		checkTime();
		// checkSensor();
	}
	c = *getc_data;
	return c;
}

// convert a string to integer, if invalid, return -1
int a2i(char *str, int n) 
{
	int i;
	int result = 0;
	for (i = 0; i < n; i++) 
	{
		if (str[i] > 0x39 || str[i] < 0x30) return -1;		
		result *= 10;
		result += str[i] - 0x30;
	}
	return result;
}

// compare 2 strings, return 1 if equal, 0 otherwise
int cmp(char *str1, char *str2, int n) 
{
	int i;
	for (i = 0; i < n; i++) 
	{
		if (str1[i] != str2[i])
			return 0;
	}
	return 1;
}

// copy the source string to target string
void copy(char *target, char *source, int n) 
{
	int i;
	for (i = 0; i < n; i++) 
	{
		target[i] = source[i];
	}
	target[i] = '\0';
}

// parse the command line string into a structured command
void parseCommand(char *str, struct cmd *result) 
{
	int n = 0;	
	while (*str == ' ') str++;
	while (*(str + n) != ' ' && *(str + n) != '\r') 
	{
		n++;
	}
	
	copy(result->name, str, n);
	str += n;
	
	n = 0;
	while (*str == ' ') str++;
	while (*(str + n) != ' ' && *(str + n) != '\r') n++;
	
	if (n > 0) 
	{	
		char arg1[n + 1]; // extra space for null terminator
		copy(arg1, str, n);
		result->arg1 = a2i(arg1, n);
		str += n;
	}
	
	n = 0;
	while (*str == ' ') str++;
	while (*(str + n) != ' ' && *(str + n) != '\r') n++;
	
	if (n > 0) 
	{	
		char arg2[n + 1]; // extra space for null terminator
		copy(arg2, str, n);
		if (*arg2 == 'S' || *arg2 == 'C')
			result->arg2 = *arg2;
		else
			result->arg2 = a2i(arg2, n);
	}
}

// interpret the command and execute if valid
void executeCommand(struct cmd *command) 
{
	if (cmp(command->name, "tr", 2)) 
	{
		if (command->arg1 != -1 && command->arg2 != -1) 
		{
			bwprintf(COM2, "terminal: move train #%d with speed %d\r\n", command->arg1, command->arg2);
			
			checkTime();
	
			lastTrainSpeed = (char)command->arg2;
			putc(COM1, (char)command->arg2); // speed
			putc(COM1, (char)command->arg1); // train #
		}
		else 
		{
			bwputstr(COM2, "terminal: incorrect arguments for TR command\r\n");
		}
	} 
	else if (cmp(command->name, "rv", 2)) 
	{
		if (command->arg1 != -1) 
		{
			bwprintf(COM2, "terminal: reverse the train #%d\r\n", command->arg1);
			
			checkTime();
			
			putc(COM1, 0); // reverse train speed			
			putc(COM1, (char)command->arg1); // train #
			
			stall(500);
			
			putc(COM1, 15); // reverse code]
			putc(COM1, (char)command->arg1); // train #
			
			stall(500);
			
			putc(COM1, lastTrainSpeed); // reverse train speed
			putc(COM1, (char)command->arg1); // train #
		}
		else 
		{
			bwputstr(COM2, "terminal: incorrect argument for RV command\r\n");
		}
	} 
	else if (cmp(command->name, "st", 2)) 
	{
		if (command->arg1 != -1 && command->arg1 < 256 && command->arg1 >= 0) 
		{
			checkTime();
			bwprintf(COM2, "terminal: switch #%d is %s\r\n", command->arg1, (switches[command->arg1] == 'S' ? "straight" : "branched"));
		}
		else {
			bwputstr(COM2, "terminal: incorrect arguments for ST command\r\n");
		}
	} 
	else if (cmp(command->name, "sw", 2)) 
	{
		if (command->arg1 != -1 && (command->arg2 == 'C' || command->arg2 == 'S')) 
		{
			bwprintf(COM2, "terminal: throw switch #%d to be %s\r\n", command->arg1, (command->arg2 == 'S' ? "straight" : "branched"));			
			
			checkTime();
			
			switches[command->arg1] = command->arg2; // save the state of the switch
			
			putc(COM1, (command->arg2 == 'S' ? 33 : 34));
			putc(COM1, (char)command->arg1);
			
			stall(250);
			putc(COM1, 32);
		}
		else 
		{
			bwputstr(COM2, "terminal: incorrect argument for SW command\r\n");
		}
	} 
	else if (cmp(command->name, "wh", 2)) 
	{	
		checkTime();
		checkSensor();	
		bwprintf(COM2, "terminal: the last triggered sensor was %c%d\r\n", lastSensorChar, lastSensorNum);
	} 
	else if (cmp(command->name, "q", 1)) 
	{
			bwputstr(COM2, "terminal: quit\r\n");
			ret = 0; // exit with code 0
	} 
	else 
	{
		bwputstr(COM2, "terminal: unknown command\r\n");
	}
}

// poll the timer and update the clock
void checkTime() 
{
	int timeValue = *timer_value;
	if (previous - timeValue >= delta) 
	{
		// update time
		previous = timeValue;
		tens++;
		secs = tens / 10;
		mins = secs / 60;			
	
		// save the cursor position (\E7), move cursor to the top (\E[<row>;<col>H), clear the line (E[2K), print time and restore cursor position (\E8)
		bwprintf(COM2, "\E7\E[1;1H\E[2K%d:%d.%d\E8", mins, secs % 60, tens % 10);
	}
}

// get input from the terminal
void checkInput() 
{	
	if (!(*flags & RXFE_MASK))  // check if something came from the terminal
	{
		char c = *data;		
		str[ind++] = c; // put the character into buffer
		
		if (c == '\r') 
		{ // enter
			bwputstr(COM2, "\r\n");
			struct cmd parsedCommand;
			parsedCommand.arg1 = -1;
			parsedCommand.arg2 = -1;
			
			parseCommand(str, &parsedCommand);	
			checkTime(); // update the time			
			executeCommand(&parsedCommand);
							
			ind = 0;
			bwputstr(COM2, "> ");
		}
		else if (c == 127) 
		{ // backspace
			bwputstr(COM2, "\E[1D\E[0K"); // erases the last character
			ind -= 2;
			if (ind < 0)
				ind = 0;
		}
		else 
		{
			if (ind == MAX_COMMAND_LENGTH) 
				ret = -1;
			putc(COM2, c);			
		}
	}
}

// check the triggered sensor
void checkSensor()
{	
	char sensors[10];
	putc(COM1, 133); 
	
	int i;
	for (i = 0; i < 10; i++) 
	{			
		sensors[i] = (char)getc(COM1);
	}

	// reorder the bytes
	char nums[10];
	nums[0] = sensors[1];
	nums[1] = sensors[2];
	nums[2] = sensors[3];
	nums[3] = sensors[4];
	nums[4] = sensors[5];
	nums[5] = sensors[6];
	nums[6] = sensors[7];
	nums[7] = sensors[8];
	nums[8] = sensors[9];
	nums[9] = sensors[0];
	
	// helper values
	char banks[10];
	banks[0] = 'A';
	banks[1] = 'A';
	banks[2] = 'B';
	banks[3] = 'B';
	banks[4] = 'C';
	banks[5] = 'C';
	banks[6] = 'D';
	banks[7] = 'D';
	banks[8] = 'E';
	banks[9] = 'E';
	
	checkTime();
	
	int j; 
	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < 8; j++)
		{
			if (nums[i] & (1 << (7 - j)))
			{
				lastSensorChar = banks[i];
				lastSensorNum = j + 1 + (8 * (i % 2));
			}
		}
	}
}

// initialize the global variables
void initialize() 
{
	// timer	
	loader = (int*)(TIMER3_BASE + LDR_OFFSET);
	control = (int*)(TIMER3_BASE + CRTL_OFFSET);
	timer_value = (int*)(TIMER3_BASE + VAL_OFFSET);
	
	*loader = 0x0fffffff;	
	*control &= ~ENABLE_MASK;
	*control = (*control | ENABLE_MASK | MODE_MASK) & ~CLKSEL_MASK;
	
	previous = 0x0fffffff;
	delta = 200;	
	mins = 0;
	secs = 0;
	tens = 0;
	
	// UART1
	line = (int *)(UART1_BASE + UART_LCRH_OFFSET);
	bwsetspeed(COM1, 2400);
	bwsetfifo(COM1, OFF);
	*line = (*line | STP2_MASK | WLEN_MASK) & ~BRK_MASK & ~PEN_MASK;
	
	// UART2
	flags = (int*)(UART2_BASE + UART_FLAG_OFFSET);
	data = (int*)(UART2_BASE + UART_DATA_OFFSET);
	bwsetfifo(COM2, ON);
	bwputstr(COM2, "\E[2J\E[2;1H> "); // clear screen (\E[2J) and move cursor
	
	// input
	ind = 0;
	ret = 1;
	
	// state
	lastTrainSpeed = 0;
	lastSensorChar = '*';
	lastSensorNum = '0';
}

// stall execution for <delay> ms
void stall(int delay) 
{
	int delayDelta = delay * 2;
	int prevTime = *timer_value;
	while (prevTime - *timer_value < delayDelta)
	{
		checkTime();
		checkInput();
		if (ret != 1)
			return;
	}
}

// reset the state of the track
void resetState()
{
	bwprintf(COM2, "Ondrej Ruttkay (20179429) - CS 452\r\n");
	bwprintf(COM2, "terminal: initializing, please wait... ");
	
	// make the trains stop
	putc(COM1, 0);
	putc(COM1, 24); // train #24
	
	stall(250);
	
	putc(COM1, 0); 
	putc(COM1, 25); // train #25
	
	stall(250);
	
	// reset the sensor banks
	putc(COM1, 133); 
	
	// show life
	char star[4];
	star[0] = '-';
	star[1] = '\\';
	star[2] = '|';
	star[3] = '/';
	int i;
	
	// reset the switches to default position (C)
	bwprintf(COM2, "-");
	for (i = 1; i <= 18; i++)
	{
		bwprintf(COM2, "\E[1D%c", star[i % 4]);
		switches[i] = 'C';
		putc(COM1, 34);
		putc(COM1, i);
		stall(200);
	}
	
	for (i = 153; i <= 156; i++)
	{
		bwprintf(COM2, "\E[1D%c", star[(i + 2) % 4]);
		switches[i] = 'C';
		putc(COM1, 34);
		putc(COM1, i);
		stall(200);
	}
	
	bwprintf(COM2, "\r\nterminal: done initializing\r\n> ");
}
